#include <elf.h>
#include <stdio.h>

#include <errno.h>
#include <string.h>

#include "crossbit/ELFMemoryImage.h"

using namespace crossbit;

ELFMemoryImage::ELFMemoryImage(	XTPStr pathname, 
        XTUint32 argc, 
        XTPStr argv[],
        XTPStr envp[] )
: MemoryImage(pathname, argc, argv, envp)
{
    mi_file = SYSCALL::XTOpen(pathname, O_RDONLY); 
    if( -1 == mi_file )
    {
        UTIL::XTLOG("open image error!\n");
        SYSCALL::XTExit(-1);
    }
}

ELFMemoryImage::~ELFMemoryImage()
{
    SYSCALL::XTClose(mi_file);
}

bool ELFMemoryImage::load()
{
    Elf32_Ehdr file_header;
    XTInt32	ret;
    XTInt32	sizeof_prog_headers;
    Elf32_Phdr *prog_headers;

    ret = SYSCALL::XTRead(mi_file, (void*)(&file_header), sizeof(Elf32_Ehdr));
    if( -1 == ret )
    {
        UTIL::XTLOG("Read ELFMemoryImage Header Error!");
        SYSCALL::XTExit(-1);
    }

    if( (file_header.e_type) != ET_EXEC )
    {

        UTIL::XTLOG("Not Executable Image!");
        SYSCALL::XTExit(-1);
    } 

    mi_entry = (file_header.e_entry);
    sizeof_prog_headers = (file_header.e_phentsize) * (file_header.e_phnum);
    prog_headers = new Elf32_Phdr[(file_header.e_phnum)]; 
    //need to replace with our function

    SYSCALL::XTLseek(mi_file, (file_header.e_phoff), SEEK_SET);

    ret = SYSCALL::XTRead(mi_file, (void*)prog_headers, sizeof_prog_headers);
    if( -1 == ret )
    {
        UTIL::XTLOG("Read Prog Header Error!");
        SYSCALL::XTExit(-1);
    } 

    Elf32_Phdr *elf_phdr;
    XTInt32 elf_prot;
    XTInt32 elf_flags;
    XTInt32 page_offset;
    XTInt32 i;

    XTMemAddr highest_addr = 0x0;
    XTUint32  highest_file_size = 0;	
    XTUint32  highest_mem_size = 0;	

    PROT_READ | PROT_WRITE;
    elf_flags = MAP_ANON | MAP_PRIVATE | MAP_FIXED;
    if( MAP_FAILED == SYSCALL::XTMmap( 0 , GLOBAL::STACK_BASE , PROT_READ | PROT_WRITE , MAP_ANON | MAP_PRIVATE | MAP_FIXED , -1 , 0 ) ){
        UTIL::XTLOG("mmap Error!");
        SYSCALL::XTExit(-1);
    }

    for( i = 0, elf_phdr = (Elf32_Phdr*)(prog_headers); i < (file_header.e_phnum); i++, elf_phdr++ )
    {
        if( (elf_phdr->p_type) != PT_LOAD )
            continue;
        elf_prot = 0; 
        if( (elf_phdr->p_flags) & PF_R )
            elf_prot |= PROT_READ;
        if( (elf_phdr->p_flags) & PF_W )
            elf_prot |= PROT_WRITE;
        if( (elf_phdr->p_flags) & PF_X )
            elf_prot |= PROT_EXEC;

        elf_flags = MAP_PRIVATE | MAP_FIXED;

        page_offset = PAGEOFFSET((elf_phdr->p_vaddr));


        SYSCALL::XTLseek(mi_file, (elf_phdr -> p_offset), SEEK_SET);

        if( -1 == SYSCALL::XTRead(mi_file, (void*)(elf_phdr -> p_vaddr), (elf_phdr -> p_filesz) )){

            UTIL::XTLOG( " read file error\n");
            SYSCALL::XTExit( -1  );
        }

        if( (elf_phdr->p_vaddr) > highest_addr )
        {
            highest_addr = PAGESTART((elf_phdr->p_vaddr));
            highest_file_size = (elf_phdr->p_filesz) + page_offset;
            highest_mem_size = (elf_phdr->p_memsz) + page_offset;
        }
    }
    delete[] prog_headers;

    //Special deal with bss segment.
    elf_prot = PROT_READ | PROT_WRITE;
    elf_flags = MAP_ANON | MAP_PRIVATE | MAP_FIXED;
    mi_brk = PAGEALIGN(highest_addr + highest_mem_size);
    SYSCALL::sys_brk = mi_brk;

    //map memory for heap & stack
    elf_prot = PROT_READ | PROT_WRITE;
    elf_flags = MAP_ANON | MAP_PRIVATE | MAP_FIXED;

    mi_stack = setupStack(GLOBAL::STACK_BASE);

    //printf("mi_stack = %x\n" , mi_stack);
    return true;
}


// just return the arg src, this function is provided only for compatibility with Crossbit V1
XTMemAddr ELFMemoryImage::map(XTMemAddr src)
{
    return src;
}


XTMemAddr ELFMemoryImage::setupStack( XTMemAddr base )
{
    XTUint32 i;
    XTMemAddr sp = base - MAX_ENVIRON;

    XTMemAddr p = sp;

    *(XTInt32*)p = (mi_argc);
    p += sizeof(XTInt32);

    XTMemAddr argv_addr = p;
    p += (mi_argc+1) * sizeof(XTPStr);

    XTMemAddr envp_addr = p;
    for( i = 0; mi_envp[i]; ++ i )
        p += sizeof(XTPStr);
    p += sizeof(XTPStr); // for NULL in the end of envp

#define NEW_AUX_ENT(id, val) \
    do { *(int*)p = (id); p += 4; *(int*)p = (val); p +=4; } while(0)

    NEW_AUX_ENT(0x10, 0xbfebf3ff);
    NEW_AUX_ENT(0x6, 0x1000);
    NEW_AUX_ENT(0x11, 0x64);
    NEW_AUX_ENT(0x3, 0x8048034);
    NEW_AUX_ENT(0x4, 0x20);
    NEW_AUX_ENT(0x5, 0x4);
    NEW_AUX_ENT(0x7, 0);
    NEW_AUX_ENT(0x8, 0);
    NEW_AUX_ENT(0x9, 0x8048100);
    NEW_AUX_ENT(0xb, 0);
    NEW_AUX_ENT(0xc, 0);
    NEW_AUX_ENT(0xd, 0);
    NEW_AUX_ENT(0xe, 0);
    NEW_AUX_ENT(0x17, 0);
    NEW_AUX_ENT(0xf, 0xbfe9550b);
    NEW_AUX_ENT(0x0, 0);


    for( i = 0; i < mi_argc; i ++ )
    {
        UTIL::XTStrcpy((XTPStr)p, mi_argv[i]);
        //printf("mi_argv = %x\ncontent = %s\n" , p , mi_argv[i] );
        ((XTPStr*)argv_addr)[i] = (XTPStr)((p));
        p += UTIL::XTStrlen(mi_argv[i]) +1;
    }
    ((XTPStr*)argv_addr)[i] = NULL;

    for( i = 0; mi_envp[i]; ++i)
    {
        UTIL::XTStrcpy((XTPStr)p, mi_envp[i]);
        //printf("mi_argv = %x\ncontent = %s\n" , p , mi_envp[i] );
        ((XTPStr*)envp_addr)[i] = (XTPStr)((p));
        p += UTIL::XTStrlen(mi_envp[i]) + 1;
    }
    ((XTPStr*)envp_addr)[i] = NULL;

    return (XTMemAddr)(sp);
}

